/* eslint-disable max-params */
import UtilsEntity from '../classes/UtilsEntity';
import {
  YFnObj,
  YForm,
  YInterception,
  YItemConfig,
  YItemRow,
} from '../interface';
import ItemConfigNode from './ItemConfigNode';
import ItemRow from './ItemRow';
import ItemRowList from './ItemRowList';
import ItemText from './ItemText';

type Item = ItemConfig | ItemRow | ItemRowList | ItemText;

export default class ItemConfig {
  data: YItemConfig;
  nodes: ItemConfigNode | undefined;
  slots: { [key: string]: ItemConfigNode } | undefined;
  next: Item | null = null;
  prev: Item | null = null;
  uniqueId: string = new UtilsEntity().uuid();

  constructor(
    data: YItemConfig,
    next: Item | null = null,
    prev: Item | null = null,
    public form: YForm
  ) {
    this.data = { ...data, uniqueId: this.uniqueId };
    this.next = next;
    this.prev = prev;
    if (data.nodes) {
      this.nodes = new ItemConfigNode(data.nodes, this.form);
    }
    if (data.slots) {
      const slots = data.slots;
      this.slots = Object.keys(slots).reduce(
        (total: { [key: string]: ItemConfigNode }, key: string) => {
          total[key] = new ItemConfigNode(slots[key], this.form);
          return total;
        },
        {}
      );
    }
  }

  setConfig(data: YItemConfig) {
    let bind: { [key: string]: any } | YFnObj = {};
    if (typeof data.bind === 'function') {
      bind = data.bind;
    } else if (typeof this.data.bind === 'object') {
      bind = Object.assign({}, this.data.bind, data.bind);
    } else if (typeof this.data.bind === 'function') {
      const lastBind = this.data.bind;
      bind = (...args) => {
        const result = lastBind(...args);
        return Object.assign({}, result, data.bind);
      };
    }

    const formItem = Object.assign(
      {},
      'formItem' in this.data ? this.data.formItem : {},
      'formItem' in data ? data.formItem : {}
    );
    const col = Object.assign({}, data.col, this.data.col);
    this.data = Object.assign(this.data, data, { formItem, bind, col });
    return this;
  }

  set(props: string | string[], key: string, value: any, keyName?: string) {
    const data = this.data;
    props = typeof props === 'string' ? [props] : props;
    const prop = this.form.createEntity.getConfigFile(this.data, 'prop');
    const isCurrent = props.length
      ? this.data.prop
        ? props?.includes(prop) || props === undefined
        : false
      : true;
    if (isCurrent) {
      const options = { [key]: value };
      const config = Object.assign(
        { type: data.type },
        keyName ? { [keyName]: options } : options
      ) as YItemConfig;
      this.setConfig(config);
    }
  }

  getConfig(interception?: YInterception) {
    let config: any = Object.assign({}, this.data);
    if (interception) {
      if (interception?.itemConfig?.['*']) {
        config = interception.itemConfig['*'](config);
      }
      if (interception?.itemConfig?.[config.type]) {
        config = interception.itemConfig[config.type](config);
      }
    }
    if (this.nodes) {
      config.nodes = this.nodes.getConfig(interception);
    }

    if (this.slots) {
      const slots = this.slots;
      config.slots = Object.keys(slots).reduce(
        (total: { [key: string]: ItemConfigNode }, key: string) => {
          total[key] = slots[key].getConfig(interception);
          return total;
        },
        {}
      );
    }

    return config;
  }

  setBind(props: string | string[], key: string, value: any) {
    if (this.data.prop) {
      this.set(props, key, value, 'bind');
    }

    if (this.nodes) {
      this.nodes.setBind(props, key, value);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.setBind(props, key, value);
      });
    }
  }

  setDisabled(disabled: boolean, props: string[] = []) {
    if (this.data.prop) {
      this.setBind(props, 'disabled', disabled);
    }

    if (this.nodes) {
      this.nodes.setDisabled(disabled, props);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.setDisabled(disabled, props);
      });
    }
  }

  setRequired(required: boolean, props: string[] = []) {
    if (this.data.prop) {
      this.set(props, 'required', required);
    }

    if (this.nodes) {
      this.nodes.setRequired(required, props);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.setRequired(required, props);
      });
    }
  }

  setOptions(prop: string, options: { [key: string]: any }[]) {
    if (this.data.prop) {
      this.setBind(prop, 'options', options);
    }

    if (this.nodes) {
      this.nodes.setOptions(prop, options);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.setOptions(prop, options);
      });
    }
  }

  appendConfigBefore(prop: string, config: YItemConfig) {
    if (this.data.prop === prop) {
      const newNode = new ItemConfig(config, this, null, this.form);

      if (this.prev) {
        this.prev.next = newNode;
        if (newNode.next) {
          newNode.next.prev = newNode;
        }
      }
      return this;
    }
    if (this.nodes) {
      this.nodes.appendConfigBefore(prop, config);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.appendConfigBefore(prop, config);
      });
    }
  }

  appendConfigAfter(prop: string, config: YItemConfig) {
    if (this.data.prop === prop) {
      const newNode = new ItemConfig(config, this.next, this, this.form);
      this.next = newNode;
    }

    if (this.nodes) {
      this.nodes.appendConfigAfter(prop, config);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.appendConfigAfter(prop, config);
      });
    }
  }

  removeRow(id: string | number) {
    if (this.nodes) {
      this.nodes.removeRow(id);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.removeRow(id);
      });
    }
  }

  appendRowBefore(
    id: string | number,
    config: YItemConfig | YItemRow | YItemRow[]
  ) {
    if (this.nodes) {
      this.nodes.appendRowBefore(id, config);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.appendRowBefore(id, config);
      });
    }
  }

  appendRowAfter(
    id: string | number,
    config: YItemRow | YItemConfig | YItemRow[]
  ) {
    if (this.nodes) {
      this.nodes.appendRowAfter(id, config);
    }

    if (this.slots) {
      Object.values(this.slots).forEach((node) => {
        node.appendRowAfter(id, config);
      });
    }
  }

  getItemConfigs() {
    let configs: YItemConfig[] = [];
    configs.push(this.data);
    if (this.nodes) {
      const nodeConfigs = this.nodes.getItemConfigs();
      if (Array.isArray(nodeConfigs)) {
        configs = configs.concat(nodeConfigs);
      }
    }

    if (this.slots) {
      const slotsConfigs = Object.values(this.slots).reduce(
        (nodes: YItemConfig[], node) => {
          const result = node.getItemConfigs();
          if (Array.isArray(result)) {
            configs = configs.concat(result);
          }
          return configs;
        },
        []
      );
      configs = configs.concat(slotsConfigs);
    }
    return configs;
  }

  getRows() {
    let itemRows: YItemRow[] = [];
    if (this.nodes) {
      const nodeConfigs = this.nodes.getRows();
      if (Array.isArray(nodeConfigs)) {
        itemRows = itemRows.concat(nodeConfigs);
      }
    }
    if (this.slots) {
      const slotsConfigs = Object.values(this.slots).reduce(
        (nodes: YItemRow[], node) => {
          const result = node.getRows();
          if (Array.isArray(result)) {
            return nodes.concat(result);
          }
          return nodes;
        },
        []
      );
      itemRows = itemRows.concat(slotsConfigs);
    }
    return itemRows;
  }
}
